/*
	jGrouse framework
	Copyright 2007 by Denis Riabtchik 
	All rights reserved.
	
	Licensed under modified BSD license. See license.txt for details
*/

/**
	Contains utilities for DOM manipulations
	@module jgrouse.dom.Element
	@requires module-jgrouse.browser.Browser
	@requires module-jgrouse.string
	@requires module-jgrouse.coreutil
*/
jgrouse.module(
{
	name : 'jgrouse.dom.Element', 
	requires : ['jgrouse.browser.Browser', 'jgrouse.string', 'jgrouse.coreutil'],
	body : function(embed)
	{
		eval(embed);
		
		var browser = jgrouse.browser.instance;
		
		if (!jgrouse.dom)
		{
			jgrouse.dom = {};
		}
		
		var garbageBin = document.createElement('div');
		garbageBin.className = '__jgrouse_garbage_bin';
		
		function cleanupGarbage()
		{
			garbageBin = null;
		}
		
		jgrouse.addUnloadHook(cleanupGarbage);
		
		/**
			Utilities for DOM manipulations
			@namespace jgrouse.dom.Element
		*/
		jgrouse.dom.Element = 
		{
			ELEMENT_NODE 			: 1,
			ATTRIBUTE_NODE 			: 2,
			TEXT_NODE				: 3,
			CDATA_SECTION_NODE		: 4,
			ENTITY_REFERENCE_NODE	: 5,
			ENTITY_NODE             : 6,
			PROCESSING_INSTRUCTION_NODE : 7,
			COMMENT_NODE			: 8,
			DOCUMENT_NODE			: 9,
			DOCUMENT_TYPE_NODE		: 10,
			DOCUMENT_FRAGMENT_NODE	: 11,
			NOTATION_NODE			: 12,
			
			/**
				Dispose DOM node.
				@function disposeDom
				@param {DOMElement} element - element to be disposed
			*/
			disposeDom : function(element)
			{
				garbageBin.appendChild(element);
				garbageBin.innerHTML = '';
			},
		
			/**
				Get attribute of DOM node
				@function {String} getAttribute
				@param {DOMElement} element - source element
				@param {String} attrName - name of attribute to read
				@returns value of read attribute
			*/
			getAttribute : function(element, attrName)
			{
				return browser.getAttribute(element, attrName);
			},
			
			/**
				Get all specified attributes pf DOM node.
				@function {DOMAttribute[]} getAttributes
				@param {DOMElement} domNode
				@returns array of DOMAttribute[] objects

			*/
			getAttributes : function(element)
			{
				return browser.getAttributes(element);
			},
			
			/**
				Removes all attributes from node
				@function clearAttributes
				@param {DOMElement} element - node to be cleared from attributes
			*/
			clearAttributes : function(element)
			{
				browser.clearAttributes(element);
			},
			
			/**
				Add or update node's attribute
				@function setAttribute
				@param {DOMElement} element - target node
				@param {String} attrName - name of attribute to set/update
				@param {String} value - data to be set
			*/
			setAttribute : function(element, attrName, value)
			{
				var lc = attrName.toLowerCase();
				if (lc == 'style')
				{
					jgrouse.dom.Element.setStyles(element, value);
				}	
				else if (lc == 'class')
				{
					element.className = value;
				}
				else
				{
					var camel = jgrouse.string.camelize(attrName);
					element.setAttribute(attrName, value)
					element.attributes[attrName].nodeValue = value;
					element[camel] = value;
				}
			},
			
			/**
				Set several attributes on node. Old attributes are being removed first.
				@function setAttributes
				@param {DOMElement} element - target node
				@param {Object} attributes - object containing names/values of attributes to set
			*/
			setAttributes : function(element, attributes)
			{
				jgrouse.dom.Element.clearAttributes(element);
				jgrouse.forEach(attributes, function(attrValue, attrName)
				{
					jgrouse.dom.Element.setAttribute(element, attrName, attrValue);
				})
			},
			
			
			_setOneStyle : function(element, style, value)
			{
				var s = jgrouse.string.camelize(style);
				var mapped = browser.mapCssProperty(s);
				element.style[mapped] = value;
			},
			
			_internalSetStyle : function(element, style, value)
			{
				if (typeof style == 'object')
				{
					jgrouse.forEach(style, function(value, key)
					{
						jgrouse.dom.Element._setOneStyle(element, key, value);
					});
				} 
				else
				{
					jgrouse.dom.Element._setOneStyle(element, style, value);
				}
			},
			
			/**
				Set CSS style(s) on DOM node. Existing styles are removed.
				@function setStyles
				
				@paramset Set one style
				@param {DOMElement} element - target node
				@param {String} style - name of CSS style
				@param {String} value - value of style
				
				@paramset Set several styles
				@param {DOMElement} element - target node
				@param {Object} obj - object containing name/value pairs of CSS styles and values
				
				@paramSet Set several styles from string
				@param {DOMElement} element - target node
				@param {String} style - textual representation of styles
			*/
			setStyles : function(element, style, value)
			{
				jgrouse.dom.Element.clearStyles(element);
				if (arguments.length == 2 && typeof style == 'string')
				{
					element.style.cssText = style;
				}
				else
				{
					jgrouse.dom.Element._internalSetStyle(element, style, value);
				}
			},
			
			/**
				Adds CSS style to DOM node.
				@function addStyle
				@paramset Add one style
				@param {DOMElement} element - target node
				@param {String} style - name of CSS style
				@param {String} value - value of style
				
				@paramset Add several styles
				@param {DOMElement} element - target node
				@param {Object} obj - object containing name/value pairs of CSS styles and values
			*/
			addStyle : function(element, style, value)
			{
				jgrouse.dom.Element._internalSetStyle(element, style, value);
			},

			/**
				Retrieve CSS styles from DOM node
				@function {Object} getStyles
				@param {DOMElement} element - target node
				@returns object containing name/value pairs for CSS styles. Names are camelized and 
				could be browser-specific.
			*/			
			getStyles : function(element)
			{
				return jgrouse.browser.instance.getStyles(element);
			},
			
			/**
				Removes CSS style from DOM node
				@function clearStyle
				@param {DOMElement} element - target node
				@param {String} styleName - name of CSS style to be removed.
			*/
			clearStyle : function(element, styleName)
			{
				jgrouse.dom.Element._setOneStyle(element, styleName, '');
			},
			
			/**
				Retrieves computed CSS style (i.e. inherited from parent nodes, classes, etc) for DOM node.
				@function getComputedStyle
				@param {DOMElement} element - target element
				@param {String} style - name of CSS style
			*/
			getComputedStyle : function(element, style)
			{
				return browser.getComputedStyle(element, jgrouse.string.camelize(style));
			},
			
			/**
				Remove all declared styles from DOM node
				@function clearStyles
				@param {DOMElement} element - target node
			*/
			clearStyles : function(element)
			{
				element.style.cssText = '';
			},
			
			/**
				Remove attribute from DOM node
				@function clearAttribute
				@param {DOMElement} element - target node
				@param {String} attr - name of attribute to remove.
			*/
			clearAttribute : function(element, attr)
			{
				if (attr == 'style')
				{
					jgrouse.dom.Element.clearStyle(element);
				}
				else
				{
					if (attr == 'class')
					{
						attr = 'className';
					}
					var v = element.removeAttribute(attr);
					if (typeof element[attr] != 'undefined')
					{
						try
						{
							v &= delete element[attr];
						}
						catch(ex)
						{
						}
					}
					return v;
				}
			},
			
			/**
				Set HTML class on DOM node. Replaces any existing classes
				@function setClass
				@param {DOMElement} className
				@param {String} className - name of class to set
			*/
			setClass : function(element, className)
			{
				element.className = className;
			},
			
			/**
				Add HTML class on DOM node. 
				@function addClassName
				@param {DOMElement} element - target node
				@param {String} className - name of class to add
			*/
			addClassName : function(element, className)
			{
				var current = element.className;
				if (current)
				{
					var arr = current.split(' ');
					if (jgrouse.search(arr, className) < 0)
					{
						arr.push(className);
						element.className = arr.join(' ');
					}
				}
				else
				{
					element.className = className;
				}
			},
			
			/**
				Remove HTML class from DOM node
				@function removeClassName
				@param {DOMElement} element - target node
				@param {String} className - name of class to remove
			*/
			removeClassName : function(element, className)
			{
				var current = element.className;
				if (current)
				{
					var arr = current.split(' ');
					var i = jgrouse.search(arr, className);
					if (i >= 0)
					{
						arr.splice(i, 1);
						element.className = arr.join(' ');
					}
				}
			},
			
			/**
				Check if DOM node has given HTML class
				@function {boolean} hasClass
				@param {DOMElement} element - target node
				@param {String} className - class name to check
				@returns true if the element has given HTML class
			*/
			hasClass : function(element, className)
			{
				var current = element.className;
				if (current)
				{
					var arr = current.split(' ');
					return jgrouse.search(arr, className) >= 0;
				}
				return false;
			},
			
			/**
				Invoke given callback for each child of the node
				@function forEach
				@param {DOMElement} element - target node
				@param {jgrouse.arrayIteratorCallback} callback - function to be called for each child node. 
				@param {Boolean} backwards - indicator if iteration should start from last child to 
					first child. Optional, defautl value is false
			*/
			forEach : function(element, callback, backwards)
			{
				var nodes = element.childNodes;
				if (backwards)
				{
					for (var i = nodes.length - 1; i >= 0; i--)
					{
						var node = nodes.item(i);
						callback(node, i);
					}
				}
				else
				{
					for (var i = 0; i < nodes.length; i++)
					{
						var node = nodes.item(i);
						callback(node, i);
					}
				}
			},
			
			/**
			 * Invoke callback function for element's child nodes 
			 * @function ?
			 * @param {DOMElement} element
			 * @param {jgrouse.arrayIteratorCallback} callback - function to be called
			 * @param {boolean} backwards - whether to start from the last child or first child
			 */
			forEachNode : function(element, callback, backwards)
			{
				var nt = jgrouse.dom.Element.ELEMENT_NODE;
				jgrouse.dom.Element.forEach(element, function(node, index){
					if (node.nodeType === nt)
					{
						callback(node, index);
					}
				}, backwards);
			},
			
            /**
             * @ifunction {boolean} traverseCallback
             * @param {DOMElement} element currently being traversed
             * @return true if element matches certaint criteria
             */			
			
			/**
				Travese the DOM tree for given element, calling callback function for each node. If the callback 
				function returns <code>true</code>, then traversing is interrupted
				@function walkTree
				@param {DOMElement} element - starting element
				@param {traverseCallback} testCallback - callback function to be called for each node
			*/
			walkTree : function(element, testCallback)
			{
				function worker(elem)
				{
					if (testCallback(elem))
					{
						throw jgrouse.$break;
					}
					jgrouse.dom.Element.forEach(elem, worker);
				}
				
				try
				{
					worker(element);
				}
				catch (ex)
				{
					if (ex !== jgrouse.$break)
					{
						throw ex;
					}
				}
			},
			
			
			
			/**
				Find DOM node matching certain criteria
				@function {DOMElement} search
				@param {DOMElement} element - starting point
				@param {traverseCallback} testCallback - callback function to be called for each node. 
				@return found node or null if no nodes were found
			*/
			
			search : function(element, testCallback)
			{
				var res = null;
				jgrouse.dom.Element.walkTree(element, function(element)
				{
					if (testCallback(element))
					{
						res = element;
						return true;
					}
					return false;
				});
				return res;
			},
			
			/**
				Find all nodes matching certain criteria.
				@function {DOMNode[]} collect
				@param {DOMNode} element - starting point
				@param {traverseCallback} testCallback - function that should be called for each node. Should 
				return <code>true</code> if the element should be added to result set
				@return array with matching elements
			*/
			collect : function(element, testCallback)
			{
				var res = [];
				jgrouse.dom.Element.walkTree(element, function(element)
				{
					if (testCallback(element))
					{
						res.push(element);
					}
				});
				return res;
			},
						
			/**
				Move all children from one DOM node to another DOM node
				@function moveChildrenToNewParent
				@param {DOMElement} dstParent - destination node
				@param {DOMElement} srcParent - source node
			*/
			moveChildrenToNewParent : function(dstParent, srcParent)
			{
				jgrouse.dom.Element.forEach(srcParent, function(dom)
				{
					var fc = dstParent.firstChild;
					if (fc)
					{
						dstParent.insertBefore(dom, fc);
					}
					else
					{
						dstParent.appendChild(dom);
					}
				}, true);
				
			},
			
			/**
				Checks if given DOM node is reacheable from document root and has style.display != 'none'.
				@function {boolean} isShowing
				@param {DOMElement} element - target node
				@returns true if the node is reacheable from document root
			*/
			isShowing : function(element)
			{
				var current = element;
				while (current && current != document)
				{
					if (current.style.display == 'none')
					{
						return false;
					}
					current = current.parentNode;
				}
				return current == document;
			},
			
			/**
			 * @function ?
			 * @param {DOMElement} domElement
			 * @param {String} align - 'left', 'top' or null
			 */
			scrollIntoView : function(domElement, align)
			{
				browser.scrollIntoView(domElement, align);
			}
		}
	}
})
